package boardgamer.common.client.board;

import android.graphics.PointF;
import android.graphics.RectF;
import boardgamer.common.board.TiledBoard;
import boardgamer.common.client.Vector;
import boardgamer.common.client.layout.HasLayout;
import boardgamer.common.client.layout.LayoutGroup;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Table.Cell;

import javax.annotation.Nullable;

import static com.google.common.base.Preconditions.checkNotNull;

/**
 * Transforms between tile coordinates and screen coordinates for tiled boards
 *
 * @author garysoed
 */
public abstract class BoardLayout<T extends HasLayout, B extends TiledBoard<T>>
    implements HasLayout {

  @Nullable protected LayoutGroup layoutGroup;
  @Nullable protected B board;

  public final void setBoard(B board) {
    layoutGroup = new LayoutGroup();

    for (Cell<Integer, Integer, T> cell : board.getBoard().cellSet()) {
      Vector screenCoordinates
          = getTileScreenCoordinates(cell.getRowKey(), cell.getColumnKey());

      // Update each tile's bounding rect
      T tile = cell.getValue();
      RectF boundingRect = tile.getBoundingRect();
      float offsetX = screenCoordinates.getX() - boundingRect.centerX();
      float offsetY = screenCoordinates.getY() - boundingRect.centerY();
      boundingRect.offset(offsetX, offsetY);
      tile.setBoundingRect(boundingRect);

      layoutGroup.add(tile).atCurrentRect();
    }

    this.board = board;
  }

  @Override
  public void setBoundingRect(RectF boundingRect) {
    checkNotNull(layoutGroup, "Cannot call setBoundingRect before calling setBoard");
    layoutGroup.setBoundingRect(boundingRect);
  }

  @Override
  public RectF getBoundingRect() {
    checkNotNull(layoutGroup, "Cannot call getBoundingRect before calling setBoard");
    return layoutGroup.getBoundingRect();
  }

  /**
   * Returns the screen coordinates for initial placement of the tiles. This only gets called when
   * calling {@link #setBoard}. This coordinate can be anywhere relative to the tile, as long as
   * it is consistent for all tiles.
   */
  @VisibleForTesting abstract Vector getTileScreenCoordinates(int tileX, int tileY);

  /**
   * Returns the tile closest to the screen coordinates. May return null if the given screen
   * coordinate is outside the board area
   */
  @Nullable
  public abstract T getTile(PointF screenCoordinate);
}
